#include <strmif.h>
#include "mkvparserstream.hpp"
#include "mkvparser.hpp"
#include <cassert>
#include <sstream>
#include <iomanip>
#ifdef _DEBUG
#include "odbgstream.hpp"
using std::endl;
#endif

namespace MkvParser
{


Stream::Stream(Track* pTrack) :
    m_pTrack(pTrack)
{
    Init();
}


Stream::~Stream()
{
}


void Stream::Init()
{
    m_pCurr = m_pTrack->GetFirst();
    m_pStop = 0;  //menas play entire stream
    m_baseTime = 0;  //all streams must always use same base time
    m_bDiscontinuity = true;
}


std::wstring Stream::GetId() const
{
    std::wostringstream os;
    
    GetKind(os);  //"Video" or "Audio"
    
    os << std::setw(3) << std::setfill(L'0') << m_pTrack->GetNumber();
    
    return os.str();
}        


std::wstring Stream::GetName() const
{    
    const Track* const t = m_pTrack;
    assert(t);
    
    if (const wchar_t* codecName = t->GetCodecName())
        return codecName;
        
    if (const wchar_t* name = t->GetName())
        return name;
        
    if (ULONG tn = t->GetNumber())
    {
        std::wostringstream os;
        os << L"Track" << tn;
        return os.str();
    }
    
    if (const char* codecId = t->GetCodecId())
    {
        std::wstring result;
        
        const char* p = codecId;
 
        while (*p)
            result += wchar_t(*p++);  //TODO: is this cast meaningful?
            
        return result;
    }
        
    return GetId();
}


__int64 Stream::GetDuration() const
{
    Segment* const pSegment = m_pTrack->m_pSegment;
    assert(pSegment);
    
    const __int64 ns = pSegment->GetDuration();  //scaled (nanoseconds)
    assert(ns >= 0);
    
    const __int64 d = ns / 100;  //100-ns ticks
    
    return d;
}


#if 0  //NOTE: moved from stream to outpin
HRESULT Stream::GetAvailable(
    LONGLONG* pEarliest, 
    LONGLONG* pLatest) const
{
    if (pEarliest)
        *pEarliest = 0;
        
    if (pLatest == 0)
        return S_OK;  //?
        
    LONGLONG& pos = *pLatest;  //units are current time format
        
    const Segment* const pSegment = m_pTrack->m_pSegment;
    
    //TODO: to make seeking optimal (for the source filter anyway -- 
    //the splitter case is different), we need to implement
    //out of order loading of clusters (assuming we have a SeekHead
    //element that tells us where the clusters are).  As of this
    //writing that isn't implemented.  Without that, we are limited 
    //to seeking within what has already been loaded.
    
    if (pSegment->Unparsed() <= 0)
        pos = GetDuration();
    else
    {    
        const Cluster* const pCluster = pSegment->GetLast();
    
        if (pCluster == 0)
        {
            pos = 0;
            return S_OK;
        }

        const __int64 ns = pCluster->GetTime();
        pos = ns / 100;  //TODO: reftime units are assumed here
    }
        
    return S_OK;
}
#endif


__int64 Stream::GetCurrPosition() const
{
    return GetCurrTime();  //TODO: for now only support reftime units
}


__int64 Stream::GetCurrTime() const
{
    if (m_pCurr == 0)  //already at end-of-stream
        return GetDuration();  //TODO: can we do better than this?
        
    const __int64 ns = m_pCurr->GetTime();
    
    const __int64 reftime = ns / 100;  //100-ns ticks
    
    return reftime;
}


__int64 Stream::GetStopPosition() const
{
    return GetStopTime();  //TODO: for now we only support reftime units
}


__int64 Stream::GetStopTime() const
{
    if (m_pStop == 0)  //end-of-stream
        return GetDuration();  //TODO: can we do better than this?
        
    const __int64 ns = m_pStop->GetTime();
    
    const __int64 reftime = ns / 100;  //100-ns ticks
    
    return reftime;
}


#if 0
void Stream::LoadCurrPosition(
    LONGLONG pos_reftime, 
    DWORD dwCurr_, 
    __int64& parse_result)
{
    const DWORD dwCurrPos = dwCurr_ & AM_SEEKING_PositioningBitsMask;
    assert(dwCurrPos != AM_SEEKING_NoPositioning);  //handled by caller
    
    //TODO: note that we load the curr posn (during a seek) by
    //sequentially load clusters, in order to determine their
    //location and size.  We only load the blocks within a cluster
    //when we actually need them -- so this is faster than paging
    //in all of the blocks in order to reach the cluster containing
    //the seek point, but it still means we must scan the file to
    //locate clusters.  It would be better if we could find the
    //clusters faster than via a mere sequential scan.  We might
    //be able to do that by using the SeekHead.  One approach would
    //be to find the corresponding keyframe in the SeekHead,
    //and the locate the containing cluster from that.  But that
    //might mean we would have to load clusters non-sequentially,
    //which we currently do not support.  (Technically we're within
    //the letter of the law using our existing approach.  We do say
    //in GetAvailable which seek positions are fast, and it's true
    //that we only need to peform a sequential scan when we attempt
    //to seek beyond which clusters have been loaded -- so if that's
    //too slow then the user shouldn't try to do that.)
    //
    //UPDATE: this algorithm doesn't work.  The problem is that
    //loading clusters takes a long time if the seek point is much
    //larger that what have currently loaded.  The worker thread
    //attempts to call PopulateSample, but the acquisition of the 
    //filter lock times out, because clusters are being loaded.
    
    parse_result = 0;  //tentative success
    
    Segment* const pSegment = m_pTrack->m_pSegment;
    
    const __int64 duration_ns = pSegment->GetDuration();
    assert(duration_ns >= 0);
    
    const __int64 pos_ns = pos_reftime * 100;
    __int64 tCurr_ns;
    
    switch (dwCurrPos)
    {
        case AM_SEEKING_IncrementalPositioning:  //applies only to stop pos
        default:
            assert(false);
            return;  

        case AM_SEEKING_AbsolutePositioning:
        {
            tCurr_ns = pos_ns;
            
            if (tCurr_ns < duration_ns)
                parse_result = pSegment->Load(tCurr_ns);

            break;
        }
        case AM_SEEKING_RelativePositioning:
        {
            if (m_pCurr)
            {
                tCurr_ns = m_pCurr->GetTime() + pos_ns;
                
                if (tCurr_ns < duration_ns)
                    parse_result = pSegment->Load(tCurr_ns);
            }
            else if (pos_ns < 0)
            {
                tCurr_ns = duration_ns + pos_ns;
                parse_result = pSegment->Load(tCurr_ns);
            }
                
            break;
        }      
    }
}
#endif


void Stream::SetCurrPosition(
    LONGLONG pos_reftime, 
    DWORD dwCurr_)
{
    const DWORD dwCurrPos = dwCurr_ & AM_SEEKING_PositioningBitsMask;
    assert(dwCurrPos != AM_SEEKING_NoPositioning);  //handled by caller
    
    Segment* const pSegment = m_pTrack->m_pSegment;
    
    const __int64 duration_ns = pSegment->GetDuration();
    assert(duration_ns >= 0);
    
    const __int64 pos_ns = pos_reftime * 100;
    __int64 tCurr_ns;
    
    switch (dwCurrPos)
    {
        case AM_SEEKING_IncrementalPositioning:  //applies only to stop pos
        default:
            assert(false);
            return;  

        case AM_SEEKING_AbsolutePositioning:
        {
            tCurr_ns = pos_ns;
            break;
        }
        case AM_SEEKING_RelativePositioning:
        {
            if (m_pCurr)
                tCurr_ns = m_pCurr->GetTime() + pos_ns;
            else
                tCurr_ns = duration_ns + pos_ns;
                
            break;
        }      
    }
    
    if (tCurr_ns <= 0)
    {
        m_pCurr = m_pTrack->GetFirst();
        m_baseTime = 0;
    }
    else if (tCurr_ns >= duration_ns)
    {
        m_pCurr = 0;
        //m_baseTime = 0;  (doesn't matter)
    }
    else
    {                
        //We are assuming that all streams arrive at this same value
        //for the time.  They all must have the same base time,
        //and the best we can do is to use the time of the cluster
        //containing time t as the base time (for all streams).
        
        const Cluster* const pCluster = pSegment->GetCluster(tCurr_ns);
        
        if (pCluster == 0)  //ns is large relative to existing clusters
            m_pCurr = 0;  //m_baseTime (doesn't matter)
        else
        {        
            m_pCurr = pCluster->GetBlock(m_pTrack); 
            
#ifdef _DEBUG
            if (m_pCurr == 0)
            {
                const Cluster* const p = pSegment->GetCluster(tCurr_ns);    

                odbgstream os;
                os << "Stream::SetCurrPosn: pCurr=NULL; pTrack->num="
                   << m_pTrack->GetNumber()
                   << " tCurr_ns="
                   << tCurr_ns;
                   
                if (p)
                    os << " pCluster[t=" << tCurr_ns << "]->time=" << p->GetTime();
                    
                os << endl;
            }
#endif      

            //TODO: we should handle the NULL case better.  GetBlock
            //returns NULL, we should search the later clusters
            //for a block with a matching track and time.

            //The base time is THE most important value in a multi-stream 
            //seek.  All streams must use the exact same base time, otherwise 
            //the times across streams will be out of sync.  It doesn't matter
            //whether the curr time is different across streams (all that means
            //is that streams begin rendering at slightly different times).
            
            if (dwCurrPos == AM_SEEKING_AbsolutePositioning)            
                m_baseTime = pCluster->GetTime();
            else
            {
                assert(dwCurrPos == AM_SEEKING_RelativePositioning);
                
                //In this case, we can set the base time equal to time t,
                //so that t - base = 0.  This means there's no delay when
                //rendering the first frame following the seek.  (This is
                //allowed because relative seeking is only permitted when
                //there is a single connected stream.)
        
                if (m_pCurr)            
                    m_baseTime = m_pCurr->GetTime();
            }
        }
    }

    m_bDiscontinuity = true;
}


void Stream::SetStopPosition(
    LONGLONG pos_reftime, 
    DWORD dwStop_)
{
    const DWORD dwStopPos = dwStop_ & AM_SEEKING_PositioningBitsMask;
    assert(dwStopPos != AM_SEEKING_NoPositioning);  //handled by caller
    
    if (m_pCurr == 0) //already at end of stream
    {
        m_pStop = 0;
        return;
    }
            
    const __int64 tCurr_ns = m_pCurr->GetTime();  //nanoseconds
    assert(tCurr_ns >= 0);

    const Cluster* const pCurrCluster = m_pCurr->m_pGroup->m_pCluster;
    pCurrCluster;
    assert(pCurrCluster);
    assert(tCurr_ns >= pCurrCluster->GetTime());
        
    Segment* const pSegment = m_pTrack->m_pSegment;    

    const __int64 duration_ns = pSegment->GetDuration();
    assert(duration_ns >= 0);

    const __int64 pos_ns = pos_reftime * 100;        
    __int64 tStop_ns;
            
    switch (dwStopPos)
    {
        default:
            assert(false);
            return;

        case AM_SEEKING_AbsolutePositioning:
        {
            tStop_ns = pos_reftime;
            break;            
        }
        case AM_SEEKING_RelativePositioning:
        {
            if (m_pStop)
                tStop_ns = m_pStop->GetTime() + pos_ns;
            else
                tStop_ns = duration_ns + pos_ns;
                                
            break;
        }      
        case AM_SEEKING_IncrementalPositioning:
        {
            if (pos_reftime <= 0)
            {
                m_pStop = m_pCurr;
                return;
            }
            
            tStop_ns = tCurr_ns + pos_ns;
            break;            
        }
    }
    
    if (tStop_ns <= tCurr_ns)
    {
        m_pStop = m_pCurr;
        return;
    }
    
    if (tStop_ns >= duration_ns)
    {
        m_pStop = 0;
        return;
    }
                
    const Cluster* pStopCluster = pSegment->GetCluster(tStop_ns);
    
    if (pStopCluster == 0)
    {
        m_pStop = 0;
        return;
    }
    
    if (pStopCluster == pCurrCluster)
    {
        pStopCluster = pSegment->GetNext(pStopCluster);
        
        if (pStopCluster == 0)
        {
            m_pStop = 0;
            return;
        }
    }
    
    //TODO: note that a NULL return from GetBlock indicates some kind of 
    //problem, either no audio in this cluster (for this audio track),
    //or no keyframe (for this video track).  A NULL value for pStop is
    //interpreted to mean "play to end of file", but this isn't necessarily
    //what we want.  We would want to keep searching clusters for a 
    //"good" block, so this really should be a loop, but we don't want
    //to search that hard, because we prefer to defer actual loads as long
    //as possible.  So under the assumption that this file has some
    //pathological format, we respond to this pathology by playing to the end.
    //(We define as "pathological" any cluster that doesn't have at least
    //one block of each track number, or doesn't have at least one keyframe.)
    
    m_pStop = pStopCluster->GetBlock(m_pTrack);
    assert((m_pStop == 0) || (m_pStop->GetTime() >= tCurr_ns));
}


void Stream::SetStopPositionEOS()
{
    m_pStop = 0;
}


}  //end namespace MkvParser
